#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include "queue.h"
#include "task.h"

#define stack_size 32768

static int next_task_id;							//id da proxima tarefa
static task_t * running_task_ptr;					//ponteiro para tarefa em execucao
static task_t * ready_tasks;						//lista de tarefas prontas
static task_t main_task;							//tarefa principal
static task_t dispatcher;							//tarefa do dispatcher
static int user_tasks; 								//quantidade de tarefas de usuário
static task_t * current_user_task = NULL;			//ponteiro para a tarefa de usuário corrente

void dispatcher_body (void * arg)
{
	task_t * next; /*breakpoint: ver valor da tarefa em execucao*/
	while (user_tasks > 0)
	{
		next = scheduler(); //
		if (next)
		{
			
			current_user_task = next;
			task_switch(next);
			//comandos depois de task_switch()
		}
	}
	task_exit(0);
}

void task_init(){
	//inicializa a biblioteca de funcoes de gerenciamento de tarefas
	//inicializa a tarefa main_task
	getcontext(&(main_task.context));					//salva o contexto
	main_task.tid = 0;									//id da tarefa principal e 0
	main_task.context.uc_link = 0;						//finalizar programa ao terminar tarefa
	next_task_id = 1;									//nenhuma tarefa esta em execucao
	running_task_ptr = &main_task;						//nenhuma tarefa esta em execucao
	ready_tasks = NULL;									//nenhuma tarefa esta em execucao
	setvbuf (stdout, 0, _IONBF, 0);						//desativa o buffer da saida padrao (stdout), usado pela funcao printf
	user_tasks = -1;									//inicia com -1 para criar dispatcher
	task_create(&dispatcher, dispatcher_body, NULL);	//criacao da tarefa dispatcher
	queue_remove((queue_t **) &ready_tasks, (queue_t *) &dispatcher); //tirar o dispatcher da fila
	#ifdef DEBUG										//se o debug estiver habilitado
	printf("task_init inicializou a biblioteca");		//mostrar na tela que a biblioteca de tarefas foi inicializada
	#endif
	}

int task_create (task_t * task_ptr, void (*start_routine)(void *),  void * arg){
 	getcontext(&(task_ptr->context));
	char * stack;										//ponteiro para a pilha
	stack = malloc (stack_size);						//aloca espaco de memoria para a pilha
	if (stack){											//se for possivel criar a pilha
		task_ptr->context.uc_stack.ss_sp = stack;		//salva o parametro stack pointer das variaveis de contexto
		task_ptr->stack_start = stack;					//salva ponteiro para inicio da pilha
		task_ptr->context.uc_stack.ss_size = stack_size;//salva o parametro de tamanho de pilha para este contexto, definido por stack_size
		task_ptr->context.uc_stack.ss_flags = 0;		//define as flags do contexto
		task_ptr->context.uc_link = 0;					//uc_link = 0 finaliza tarefa
	} 
	else{												//se nao for possivel criar a pilha
		perror("error creating stack: ");				//avisa ao usuario erro
		#ifdef DEBUG									//se o debug estiver habilitado
		printf("task_create falhou em executar");		//mostrar na tela que a funcao foi executada e criou a tarefa
		#endif
		return (-1);									//valor de retorno em caso de erro e -1
	}
	makecontext(&(task_ptr->context), (void (*) (void)) start_routine, 1, arg);
	task_ptr->tid = next_task_id;						//dar id para tarefa
	next_task_id++;										//ir para a proxima id livre
	task_ptr->next = NULL;								//nao esta numa fila
	task_ptr->prev = NULL;
	task_ptr->priority = 0;								//a prioridade da tarefa e 0
	queue_append ((queue_t **) &ready_tasks, (queue_t *) task_ptr);
														//adicionar tarefa para fila de tarefas
	user_tasks++;
	#ifdef DEBUG										//se o debug estiver habilitado
	printf("task_create criou a tarefa %d", task_ptr->tid);	
														//mostrar na tela que a funcao foi executada e criou a tarefa
	#endif
	return 0;
}

int task_switch (task_t *task_ptr){
	/*alterna para o processamento de outra tarefa 
	task_ptr aponta para a tarefa a ser processada */
	task_t * old_task_ptr = running_task_ptr;		//a tarefa agora corrente sera suspensa
	running_task_ptr = task_ptr;					//a nova tarefa corrente e a tarefa apontada por task_ptr

	#ifdef DEBUG									//se o debug estiver habilitado
	printf("task_switch encerrou tarefa %d e iniciou tarefa %d",old_task_ptr->tid, task_ptr->tid);
													//mostrar na tela que a funcao foi executada e trocou de tarefa em execucao
	#endif
	ucontext_t * old_context = &old_task_ptr->context;
	ucontext_t * new_context = &task_ptr->context;
	return swapcontext (old_context, new_context); //retorna 0 se foi possivel fazer troca de contexto
	/*ao executar, todas as prioridades retornam a 0*/
}

void task_exit (int exit_code){
	//finaliza uma tarefa
	//retorna para a tarefa principal
	int id_finalizado = running_task_ptr->tid;
	user_tasks--;
	if (id_finalizado == 1)													//se for o dispatcher que está chamando esta funcao
	{
		task_switch(&main_task);											//retornar para a tarefa principal
	} else {																//senao, retornar ao dispatcher
		task_switch(&dispatcher);
	}
	#ifdef DEBUG															//se o debug estiver habilitado
	printf("task_exit encerrou tarefa %d \n", id_finalizado);				//mostrar na tela que a funcao foi executada e finalizou a tarefa
	#endif
}

int task_id(){
	#ifdef DEBUG															//se o debug estiver habilitado
	printf("task_id retornou %d como tarefa ativa", current_user_task->tid);//mostrar na tela qual é a tarefa ativa
	#endif
	return current_user_task->tid;
}

void task_yield ()
{		
		task_switch(&dispatcher);											//retorna para o dispatcher
}

task_t * scheduler(){
	task_t * cursor_t = ready_tasks;										//cursor para analise de tarefa
	task_t * high_prio_task = ready_tasks;									//tarefa com maior prioridade, se nenhuma tarefa tiver prioridade maior que o primeiro elemento da
																			//fila de tarefas prontas, retornará o primeiro elemento da fila de tarefas prontas
	int high_prio = 20;														//inicia com menor prioridade
	
	if (current_user_task != NULL){											//se tiver alguma tarefa em execucao
		high_prio = current_user_task->priority;							//a maior prioridade e dessa tarefa
	}

	do{																		//loop para procurar a proxima tarefa a ser executada
		if (high_prio > cursor_t->priority){								//se o cursor esta num elemento de prioridade maior que a maior prioridade ate agora (em um valor menor)
			high_prio_task = cursor_t;										//a tarefa de maior prioridade e essa
			high_prio = cursor_t->priority;									//atualiza maior prioridade
		}
		cursor_t = cursor_t->next;											//va para a proxima tarefa
	}while (cursor_t != ready_tasks);
	
	//envelhecendo as tarefas
	cursor_t = ready_tasks;
	do{
		if (cursor_t != current_user_task && cursor_t->priority > -20){		//se não for a tarefa corrente e a prioridade não for o limite minimo
			cursor_t->priority--;											//aumente a prioridade
		}
		cursor_t = cursor_t->next;											//va para a proxima tarefa
	}while (cursor_t != ready_tasks);
	return high_prio_task;
}

int task_nice (int nice_level){
//tarefa utilizada para alterar a prioridade da tarefa corrente
int old_priority = running_task_ptr->priority;							//armazena prioridade atual da tarefa em execucao
if (nice_level > -20 && nice_level < 20)								//se a nova prioridade estiver dentro dos limites
	running_task_ptr->priority=nice_level;								//altera prioridade da tarefa em execucao para nice_level
if (nice_level < -20)													//se setiver abaixo do limite minimo, a prioridade e -20
	running_task_ptr->priority = -20;
if (nice_level > 20)													//se estiver acima do limite maximo, a prioridade e 20
	running_task_ptr->priority = 20;
return old_priority;													//ao terminar, retornar a prioridade antiga da tarefa corrente
}

void print_priority(){
	task_t * cursor_t  = ready_tasks;
	do {
		printf("executed task %d with priority %d\n", cursor_t->tid, cursor_t->priority);
		cursor_t = cursor_t->next;
	}while (cursor_t != ready_tasks);
}
